/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ola_sdk.h"
#include <stddef.h>
#include "ola_framework.h"
#include "ola_log.h"
#include "ola_error.h"
#include "securec.h"
#include "ola_pal_os.h"
#include "ola_link.h"
#include "ola_cloud.h"
#include "ola_network.h"
#include "ola_data.h"
#include "ola_def.h"
#include "ola_device_model.h"
#include "ola_provisioning.h"
#include "ola_service.h"
#include "ola_cloud_m2m.h"

#define WAIT_OS_TASK_CREATE_TIME_SECOND 10
#define OLA_MS_PER_SECOND               1000
#define WAIT_FRAMEWPRK_STOP_TIME_SECOND 30

typedef enum {
    FRAMEWORK_HANDLE_TYPE_STOP = 0,
    FRAMEWORK_HANDLE_TYPE_RESET,
} FrameworkHandleType;

static int QueryDataHandler(OlaQueryType type, OlaQueryData *data);
static int OlaFrameworkStopHandler(void);
static int OlaFrameworkResetHandler(void);
static int OlaSetLogLevelHandler(OlaLogLevel level);
static int OlaFrameworkMainHandler(void);
static int RestoreHandler(void);
static bool g_olaInitFlag = false;
static OlaCallback g_olaCb;
static OlaInitParam g_olaParam;
static bool g_resetFlag = false;
static bool g_stopFlag = false;

const static OlaLifeCycleManger g_olaHandler = {
    .start = OlaFrameworkMainHandler,
    .stop = OlaFrameworkStopHandler,
    .reset = OlaFrameworkResetHandler,
    .restore = RestoreHandler,
    .query = QueryDataHandler,
    .setLogLevel = OlaSetLogLevelHandler,
    .reportService = OlaReportDeviceProperties,
};

const static OlaFrameworkInfo g_framework = {
    .provisioning = {
        .process = OlaProvisioningStart,
        .stop = OlaProvisioningStop
    },
    .connectCloud = {
        .process = OlaConnectCloudProcess,
        .stop = OlaConnectCloudExit
    },
    .connectWifi = {
        .process = OlaWaitNetworkConnected,
        .stop = OlaWaitNetworkConnectedExit
    },
    .m2mRuning = {
        .process = OlaCloudM2mProcess,
        .stop = OlaCloudM2mExit
    },
    .resident = true,
};

static int QueryRegisterState(OlaQueryData *data)
{
    if (OlaIsRegister()) {
        data->regState = OLA_REGISTER_STATE_REG;
    } else {
        data->regState = OLA_REGISTER_STATE_UNREG;
    }
    return OLA_OK;
}

static int QueryRuningState(OlaQueryData *data)
{
    int ret = OLA_OK;
    switch (GetFrameworkState()) {
        case OLA_FRAMEWORK_STATE_NOT_RUNING:
            data->runingState = OLA_RUNING_STATE_NOT_RUNING;
            break;
        case OLA_FRAMEWORK_STATE_INIT:
        case OLA_FRAMEWORK_STATE_NETCFG:
            data->runingState = OLA_RUNING_STATE_NETCFG;
            break;
        case OLA_FRAMEWORK_STATE_CONNECT_WIFI:
            data->runingState = OLA_RUNING_STATE_WAITING_NETWORK_CONNECTED;
            break;
        case OLA_FRAMEWORK_STATE_CONNECT_CLOUD:
            data->runingState = OLA_RUNING_STATE_CONNECT_CLOUD;
            break;
        case OLA_FRAMEWORK_STATE_M2M_RUNING:
            data->runingState = OLA_RUNING_STATE_M2M_RUNING;
            break;
        default:
            ret = OLA_SDK_ERR_GET_FRAMEWORK_STATE;
            break;
    }
    return ret;
}

static int QueryDataHandler(OlaQueryType type, OlaQueryData *data)
{
    if (!g_olaInitFlag) {
        OlaLogWarning("sdk not init");
        return OLA_SDK_ERR_NOT_INIT;
    }
    int ret = OLA_COM_ERR_INVALID_ARGUMENT;
    switch (type) {
        case OLA_QUERY_TYPE_REGISTER_STATE:
            return QueryRegisterState(data);
        case OLA_QUERY_TYPE_RUNING_STATE:
            return QueryRuningState(data);
        default:
            OlaLogError("invalid type");
            break;
    }
    return ret;
}

static int RestoreHandler(void)
{
    if (!g_olaInitFlag) {
        OlaLogWarning("sdk not init");
        return OLA_SDK_ERR_NOT_INIT;
    }
    int ret = OlaRestoreFactorySettings();
    if (ret != OLA_OK) {
        OlaLogCrit("restore factory failed");
        return ret;
    }
    (void)OlaFrameworkReset(NULL);
    return 0;
}

static void OlaMain(void *arg)
{
    (void)arg;
    OlaFrameworkMain();
}

static void SetFramworkFlag(FrameworkHandleType type, bool flag)
{
    switch (type) {
        case FRAMEWORK_HANDLE_TYPE_STOP:
            g_stopFlag = flag;
            break;
        case FRAMEWORK_HANDLE_TYPE_RESET:
            g_resetFlag = flag;
            break;
        default:
            break;
    }
    return;
}

static void FrameworkStopHandle(OlaHandleState state)
{
    if (state == OLA_HANDLE_STATE_OK) {
        SetFramworkFlag(FRAMEWORK_HANDLE_TYPE_STOP, true);
    }
}

static void FrameworkResetHandle(OlaHandleState state)
{
    if (state == OLA_HANDLE_STATE_OK) {
        SetFramworkFlag(FRAMEWORK_HANDLE_TYPE_RESET, true);
    }
}

static void FrameworkTrigger(FrameworkHandleType type)
{
    switch (type) {
        case FRAMEWORK_HANDLE_TYPE_STOP:
            OlaFrameworkStop(FrameworkStopHandle);
            break;
        case FRAMEWORK_HANDLE_TYPE_RESET:
            OlaFrameworkReset(FrameworkResetHandle);
            break;
        default:
            break;
    }
    return;
}

static bool GetFramworkFlag(FrameworkHandleType type)
{
    switch (type) {
        case FRAMEWORK_HANDLE_TYPE_STOP:
            return g_stopFlag;
            break;
        case FRAMEWORK_HANDLE_TYPE_RESET:
            return g_resetFlag;
            break;
        default:
            break;
    }
    return false;
}

static int FrameworkRuningHandler(FrameworkHandleType type)
{
    SetFramworkFlag(type, false);
    unsigned int sec = 0;
    do {
        /* 每隔10s触发一次 */
        if (sec % 10 == 0) {
            FrameworkTrigger(type);
        }
        OlaSleepMs(OLA_MS_PER_SECOND);
        if (GetFramworkFlag(type)) {
            return 0;
        }
        ++sec;
    } while (sec <= WAIT_FRAMEWPRK_STOP_TIME_SECOND);
    return -1;
}

static int OlaFrameworkStopHandler(void)
{
    if (!g_olaInitFlag) {
        OlaLogWarning("sdk not init");
        return OLA_SDK_ERR_NOT_INIT;
    }
    return FrameworkRuningHandler(FRAMEWORK_HANDLE_TYPE_STOP);
}

static int OlaFrameworkResetHandler(void)
{
    if (!g_olaInitFlag) {
        OlaLogWarning("sdk not init");
        return OLA_SDK_ERR_NOT_INIT;
    }
    return FrameworkRuningHandler(FRAMEWORK_HANDLE_TYPE_RESET);
}

static int OlaFrameworkMainHandler(void)
{
    if (!g_olaInitFlag) {
        OlaLogWarning("sdk not init");
        return OLA_SDK_ERR_NOT_INIT;
    }

    if (GetFrameworkState() != OLA_FRAMEWORK_STATE_NOT_RUNING) {
        OlaLogWarning("sdk framework already runing");
        return OLA_SDK_ERR_ALREADY_RUNING;
    }

    OlaTaskParam param = {
        .taskName = OLA_STRING_MAIN_TASK_NAME,
        .level = g_olaParam.taskPara.taskPriority,
        .stackSize = g_olaParam.taskPara.taskSize,
    };
    /* 创建线程拉起OlaFrameworkMain */
    OlaTask task = OlaTaskCreate(OlaMain, NULL, &param);
    if (task == NULL) {
        OlaLogEmerg("create task error");
        return OLA_OS_ERR_CREATE_TASK;
    }

    unsigned int i;
    for (i = 0; i < WAIT_OS_TASK_CREATE_TIME_SECOND; ++i) {
        if (GetFrameworkState() != OLA_FRAMEWORK_STATE_NOT_RUNING) {
            break;
        }
        OlaSleepMs(OLA_MS_PER_SECOND);
        OlaLogInfo("waiting ola task create...");
    }
    if (i == WAIT_OS_TASK_CREATE_TIME_SECOND) {
        OlaLogEmerg("create task overtime");
        return OLA_SDK_ERR_CREATE_MAIN_TASK_OVERTIME;
    }
    return 0;
}

static int OlaSetLogLevelHandler(OlaLogLevel level)
{
    if (!g_olaInitFlag) {
        OlaLogWarning("sdk not init");
        return OLA_SDK_ERR_NOT_INIT;
    }
    return OlaSetLogLevel(level);
}

int OlaGetDeviceData(OlaDeviceDataType type, OlaData *out)
{
    if (!g_olaInitFlag) {
        OlaLogAlert("sdk not init");
        return OLA_SDK_ERR_NOT_INIT;
    }
    if (g_olaCb.getDeviceData == NULL) {
        OlaLogWarning("device data cb not reg");
        return OLA_SDK_ERR_CALLBACK_NULL;
    }
    return g_olaCb.getDeviceData(type, out);
}

int OlaCtrlDeviceService(const OlaData *json, OlaReqId id)
{
    if (!g_olaInitFlag) {
        OlaLogAlert("sdk not init");
        return OLA_SDK_ERR_NOT_INIT;
    }
    if (g_olaCb.ctrlDeviceService == NULL) {
        OlaLogWarning("ctrl cb not reg");
        return OLA_SDK_ERR_CALLBACK_NULL;
    }

    return g_olaCb.ctrlDeviceService(json, id);
}

int OlaQueryDeviceService(const OlaData *json, OlaData *out)
{
    if (!g_olaInitFlag) {
        OlaLogAlert("sdk not init");
        return OLA_SDK_ERR_NOT_INIT;
    }
    if (g_olaCb.queryDeviceService == NULL) {
        OlaLogAlert("query cb not reg");
        return OLA_SDK_ERR_CALLBACK_NULL;
    }

    return g_olaCb.queryDeviceService(json, out);
}

void OlaNotifySdkEvent(OlaEventCode code)
{
    if (!g_olaInitFlag) {
        OlaLogAlert("sdk not init");
        return;
    }
    if (g_olaCb.notifySdkEvent == NULL) {
        OlaLogInfo("notify cb not reg");
        return;
    }
    g_olaCb.notifySdkEvent(code);
    return;
}

void OlaDiagnosisLogOutput(unsigned int errcode, const OlaData *msg)
{
    if (!g_olaInitFlag) {
        OlaLogAlert("sdk not init");
        return;
    }
    if (g_olaCb.diagnosisLogOutput == NULL) {
        OlaLogInfo("diagnosis cb not reg");
        return;
    }

    g_olaCb.diagnosisLogOutput(errcode, msg);
    return;
}

static int GlobalVariableInit(const OlaInitParam *para, const OlaCallback *cb)
{
    g_olaInitFlag = true;
    (void)memset_s(&g_olaCb, sizeof(OlaCallback), 0, sizeof(OlaCallback));
    if (memcpy_s(&g_olaCb, sizeof(OlaCallback), cb, sizeof(OlaCallback)) != EOK) {
        OlaLogError("memcpy error");
        return OLA_SECUREC_ERR_MEMCOPY;
    }
    (void)memset_s(&g_olaParam, sizeof(OlaInitParam), 0, sizeof(OlaInitParam));
    if (memcpy_s(&g_olaParam, sizeof(OlaInitParam), para, sizeof(OlaInitParam)) != EOK) {
        OlaLogError("memcpy error");
        return OLA_SECUREC_ERR_MEMCOPY;
    }
    return OLA_OK;
}

static void GlobalVariableDenit(void)
{
    g_olaInitFlag = false;
}

static int OlaComponentsInit(const OlaInitParam *para)
{
    int ret;
    ret = OlaDataInit();
    if (ret != OLA_OK) {
        OlaLogEmerg("data init error[%d]", ret);
        return ret;
    }
    ret = OlaCoapInit(para->runingPara.coapRecvBufSize, para->runingPara.coapSendBufSize);
    if (ret != OLA_OK) {
        OlaLogEmerg("coap init error[%d]", ret);
        return ret;
    }
    ret = OlaDevInfoInit(&para->devPara);
    if (ret != OLA_OK) {
        OlaLogEmerg("devinfo init error[%d]", ret);
        return ret;
    }
    ret = OlaProvisioningInit(&para->netcfgPara);
    if (ret != OLA_OK) {
        OlaLogEmerg("netcfg init error[%d]", ret);
        return ret;
    }
    OlaLinkInit();

    return OLA_OK;
}

static void OlaComponentsDeinit(void)
{
    int ret;
    ret = OlaDataDeinit();
    if (ret != OLA_OK) {
        OlaLogCrit("data deinit error");
    }
    ret = OlaCoapDeinit();
    if (ret != OLA_OK) {
        OlaLogCrit("coap deinit error");
    }
}

int OlaInit(const OlaInitParam *para, const OlaCallback *cb)
{
    if ((para == NULL) || (cb == NULL)) {
        OlaLogWarning("invalid param");
        return OLA_COM_ERR_INVALID_ARGUMENT;
    }
    if (g_olaInitFlag) {
        OlaLogWarning("sdk already init");
        return OLA_SDK_ERR_ALREADY_INIT;
    }
    OlaLogNotice("SDK version %s", CFG_SDK_VER);
    int ret;
    do {
        ret = GlobalVariableInit(para, cb);
        if (ret != OLA_OK) {
            OlaLogEmerg("glovbal variable init error");
            break;
        }
        ret = OlaComponentsInit(para);
        if (ret != OLA_OK) {
            OlaLogEmerg("component init error");
            break;
        }
        ret = OlaFrameworkHookRegister(&g_framework);
        if (ret != OLA_OK) {
            OlaLogEmerg("framework hook reg error");
            break;
        }
        return &g_olaHandler;
    } while (0);

    (void)OlaDeinit();
    return NULL;
}

int OlaDeinit(void)
{
    if (GetFrameworkState() != OLA_FRAMEWORK_STATE_NOT_RUNING) {
        OlaLogWarning("sdk framework already runing, cannot deint");
        return OLA_SDK_ERR_ALREADY_RUNING;
    }
    OlaComponentsDeinit();
    GlobalVariableDenit();
    return OLA_OK;
}